package com.ofek.eventos.net.ocsf.server;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.ofek.eventos.net.Request;
import com.ofek.eventos.net.ServiceCallBack;
import com.ofek.utils.Log;

/**
 * Notes to extenders of this class:
 * 1. service methods are being invoked from multiple threads
 * 2. service methods cant get primitives parameters, use wrappers for all primitive types
 * @author Ofek Ron
 *
 */
public class Service {
	private final API api;
	private final Server server;

	public Service(final int port,final API api) throws IOException {
		server = new Server( port,this);
		this.api = api;
	}

	/**
	 * invoke and forget 
	 * @param r
	 */
	public void invoke(final Request r) {
		final int n = r.params.length;
		final Class<?>[] classes = new Class<?>[n];
		for (int i = 0 ; i < n ; i++) {
			classes[i]=r.params[i].getClass();
		}
		try {
			final Method declaredMethod = api.getClass().getDeclaredMethod(r.serviceName, classes);
			declaredMethod.invoke(api, r.params);
		} catch (final Exception e) {
			e.printStackTrace();
		}
	}

	private ExecutorService workers = Executors.newCachedThreadPool();

	/**
	 * async invocation
	 * @param r
	 * @param scb
	 */
	public void handle(final Request r,final ServiceCallBack scb) {
		final int n = r.params.length;
		final Class<?>[] classes = new Class<?>[n];
		for (int i = 0 ; i < n ; i++) {
			if (r.params[i]==null) classes[i]=null;
			else classes[i]=r.params[i].getClass();
		}
		try {
			Method method = null;
			try {
				method=api.getClass().getDeclaredMethod(r.serviceName, classes);
			} catch (final Exception e) {
				final Method[] declaredMethods = api.getClass().getDeclaredMethods();

				final ArrayList<Method> candidates = new ArrayList<Method>();
				for (final Method m : declaredMethods) {
					if (m.getName().equals(r.serviceName)) 
						candidates.add(m);
				}
				int max = 0;

				for (final Method m : candidates) {
					int score = 0;
					final Class<?>[] parameterTypes = m.getParameterTypes();
					for (final Class c : parameterTypes) 
						for (final Class c2 : classes) 
							if (c.equals(c2))
								score++;
					if (score>=max) {
						method = m;
						max = score;
					}
				}
			}

			if (method==null) { 
				Log.d("Cannot understand request!",r);
				return;
			}
			final Method finalmethod = method;
			workers.execute(new Runnable() {
				@Override
				public void run() {
					try {

						scb.onResult(finalmethod.invoke(api, r.params));
					} catch (final Exception e) {
						e.printStackTrace();
						scb.onError(e);
					}
				}
			});
		} catch (final Exception e) {
			e.printStackTrace();
			scb.onError(e);
		}
	}
	/**
	 * sync invocation
	 * @param r
	 * @return
	 */
	public Object handle(final Request r) {
		final int n = r.params.length;
		final Class<?>[] classes = new Class<?>[n];
		for (int i = 0 ; i < n ; i++) {
			classes[i]=r.params[i].getClass();
		}
		try {
			final Method declaredMethod = api.getClass().getDeclaredMethod(r.serviceName, classes);
			return declaredMethod.invoke(this, r.params);
		} catch (final Exception e) {
			e.printStackTrace();
			return e;
		}

	}

	public void close() {
		server.stopListening();
		try {
			server.close();
		} catch (final IOException e) {
			e.printStackTrace();
		}
		workers.shutdown();
		workers = null;
	}


}
